package gov.fnal.xpathsearch;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

import org.apache.commons.cli.CommandLine;
import gov.fnal.xpath2ldap.XqueryxStaxParser;
import gov.fnal.xpath2ldap.Pair;

import javax.xml.stream.XMLStreamException;

public class XQueryToXPath {

    ///home/tdh/source/osg/discovery/xpathsearch/src/main/bin/srm_copy_cmd.xq
    ///home/tdh/source/osg/discovery/xpathsearch/src/main/bin/storage_element_ids.xq
    static StringBuilder expr_str = new StringBuilder()
            .append("xquery version \"1.0\"").append(";\n")
            .append("declare variable $vo as xs:string external;").append("\n")
            .append("string-join(").append("\n")
            .append("for $endpoint in //GlueSE/GlueSEControlProtocol[@GlueSEControlProtocolVersion='2.2.0']/@GlueSEControlProtocolEndpoint").append("\n")
            .append("  let $size := \"goo\"").append("\n")
            .append("  for $rootpath in $endpoint/../../GlueSA[@GlueSAFreeOnlineSize!=$size]/GlueVOInfo[@GlueVOInfoAccessControlBaseRule=$vo or @GlueVOInfoAccessControlBaseRule=$upper or @GlueVOInfoAccessControlBaseRule=$lower]/@GlueVOInfoPath").append("\n")    
            .append("    return concat(\"srm\", substring($endpoint, 6), \"?SFN=\", $rootpath,  \"/TESTFILE\", \"&#xA;\"),").append("\n")
            .append(" \"\")");
    static String expr = expr_str.toString();
    public static String EMPTY="";
    public static String SLASH_SLASH="//";
    public static String EQUALS="=";
    public static String BACKSLASH_BACKSLASH_DOLLAR ="\\$";
    public static String EQUALS_BACKSLASH_BACKSLASH_DOLLAR="=\\$";
    public static String NEQUALS_BACKSLASH_BACKSLASH_DOLLAR="!=\\$";
    public static String LT_BACK_BACKSLASH_BACKSLASH_DOLLAR="<\\$";
    public static String LT_EQUALS_BACKSLASH_BACKSLASH_DOLLAR="<=\\$";
    public static String GT_BACK_BACKSLASH_BACKSLASH_DOLLAR=">\\$";
    public static String GT_EQUALS_BACKSLASH_BACKSLASH_DOLLAR=">=\\$";
    public static String NL="\n";
    public static String NL_REGEX="\\n";
    public static String DECLARE_VARIABLE="declare variable";
    public static String LET="let ";
    public static String SPACE=" ";
    public static String EXTERNAL="external;";
    public static String DOLLAR="$";
    public static String XS_STRING="xs:string";
    
    public static Pattern EQ_SUBST = Pattern. compile("/(@.*=.*)");
    public static Pattern NE_SUBST = Pattern. compile("/(@.*!=.*)");
    public static Pattern LT_SUBST = Pattern. compile("/(@.*<.*)");
    public static Pattern LTE_SUBST = Pattern. compile("/(@.*<=.*)");
    public static Pattern GT_SUBST = Pattern. compile("/(@.*>.*)");
    public static Pattern GTE_SUBST = Pattern. compile("/(@.*>=.*)");
    public static Pattern[] ATTR_SUBST = new Pattern[]{EQ_SUBST, NE_SUBST, LT_SUBST, LTE_SUBST, GT_SUBST, GTE_SUBST};

    public static void main (String[] args) {

        if(args.length==0)  {
            args = new String[]{"--help"};
        }

        String responses = processXQueryExpression(args);
        if(responses.equals("0\n")) {
          System.err.println(responses);
        } else {
          System.out.println(responses);
        }
    }

    public static String processXQueryExpression(String[] args) {
        CommandLine cmdline = XQueryToXPathOptions.processArguments(args);
        return processXQueryExpression(cmdline);
    }

    @SuppressWarnings("unchecked")
    public static String processXQueryExpression(CommandLine cmdline) {
        StringBuilder responses = new StringBuilder();
        Set<String> xqueries = new HashSet<String>();
        Set<String> allXPaths = new HashSet<String>();
        List<String> lFullPaths;
        List<String> argslist = cmdline.getArgList();
        Map<String, String> params_ticks = new HashMap<String, String>();
        Map<String, String> params_no_ticks = new HashMap<String, String>();
        Map<String, String> params_regex = new HashMap<String, String>();
        Map<String, Boolean> params_useticks = new HashMap<String, Boolean>();
        //Map<String, String> params_undefined = new HashMap<String, String>();
        Set<String> params_undef = new HashSet<String>();
        //Parameters
        if(argslist.size()!=0) {
            for (String parameter : argslist) {
                String[] paramarr = parameter.split(EQUALS);
                if(paramarr.length!=2) {
                    System.err.println("Parameter " + parameter + " parsed to length greater than 2.");
                    System.exit(1);
                } else {
                    params_ticks.put(paramarr[0], "'"+paramarr[1]+"'");
                    params_no_ticks.put(paramarr[0], paramarr[1]);
                    params_regex.put(paramarr[0], BACKSLASH_BACKSLASH_DOLLAR +paramarr[0]);
                    //params_dollar.put(paramarr[0], DOLLAR+paramarr[0]);
                    params_useticks.put(paramarr[0], Boolean.TRUE);
                }
            }
        }

        if(cmdline.hasOption("test")) {
            String xquery_expression = expr;
            System.err.println(xquery_expression);
            lFullPaths = extractXPaths(xquery_expression);
            allXPaths.addAll(lFullPaths);
        } else {
            //From files
            String[] xqueryfiles = cmdline.getOptionValues(XQueryToXPathOptions.XQUERYFILE);
            if (xqueryfiles!=null) {
                for (String xqueryfile : xqueryfiles) {
                    String xquery_expression = getContents(xqueryfile);
                    xqueries.add(xquery_expression);
                }
            }
            //From strings
            String[] xquerystrings = cmdline.getOptionValues(XQueryToXPathOptions.XQUERYSTRING);
            if (xquerystrings!=null) {
                for (String xquery_expression : xquerystrings) {
                    xqueries.add(xquery_expression);
                }
            }
            //Parse
            for (String xquery_expression : xqueries) {
                System.err.println(xquery_expression);
                //Calls xqueryx methods
                lFullPaths = extractXPaths(xquery_expression);
                //Whether params take tick marks, as determined by their type
                String[] xqlines = xquery_expression.split(NL_REGEX);
                Set<String> args_names = new HashSet<String>();
                args_names.addAll(params_useticks.keySet());
                for (String line : xqlines) {
                    line = line.trim();
                    if(line.startsWith(DECLARE_VARIABLE)) {
                        String[] words = line.split(SPACE);
                        //if(words.length<6) continue; //Removed -otherwise cannot use functions of external variables here in xpaths.
                        //if(!EXTERNAL.equals(words[5])) continue;
                        String declared_name = words[2].substring(1);
                        if(args_names.contains(declared_name)) {
                            if(!XS_STRING.equals(words[4])) {
                                params_useticks.put(declared_name, Boolean.FALSE);
                            }
                            args_names.remove(declared_name);
                            //if(args_names.isEmpty()) break;
                        } else {
                            params_undef.add(declared_name);
                            System.err.println("Variable parameter " + declared_name);
                        }
                    }else if(line.startsWith(LET)) {
                        String[] words = line.split(SPACE);
                        String declared_name = words[1].substring(1);
                        params_undef.add(declared_name);
                    }
                }
                for (String name : args_names) {
                    System.err.println("Expression did not contain parameter " + name);
                }
                //Remove extraneous paths
                Set<String> lFullPathsModified = new HashSet<String>();
                for (String lpath : lFullPaths) {
                    if(lpath.startsWith(SLASH_SLASH)) {
                        lFullPathsModified.add(lpath);
                    } else {
                        System.err.println("Skipping " + lpath);
                    }
                }
                //Take care of variables
                lFullPaths = new ArrayList<String>();
                for (String lpath : lFullPathsModified) {
                    //Replace with parameter values
                    for (String name : params_regex.keySet()) {
                        if(params_useticks.get(name)) {
                            lpath = lpath.replaceAll(params_regex.get(name), params_ticks.get(name));
                        } else {
                            lpath = lpath.replaceAll(params_regex.get(name), params_no_ticks.get(name));
                        }
                    }
                    //Remove if parameter is not defined
                    for (String undef : params_undef) {                        
                        lpath = lpath.replaceAll("\\b"+NEQUALS_BACKSLASH_BACKSLASH_DOLLAR+undef+"\\b", EMPTY);
                        lpath = lpath.replaceAll("\\b"+LT_EQUALS_BACKSLASH_BACKSLASH_DOLLAR+undef+"\\b", EMPTY);
                        lpath = lpath.replaceAll("\\b"+GT_EQUALS_BACKSLASH_BACKSLASH_DOLLAR+undef+"\\b", EMPTY);
                        lpath = lpath.replaceAll("\\b"+EQUALS_BACKSLASH_BACKSLASH_DOLLAR+undef+"\\b", EMPTY);
                        lpath = lpath.replaceAll("\\b"+LT_BACK_BACKSLASH_BACKSLASH_DOLLAR+undef+"\\b", EMPTY);
                        lpath = lpath.replaceAll("\\b"+GT_BACK_BACKSLASH_BACKSLASH_DOLLAR+undef+"\\b", EMPTY);
                    }
                    lFullPaths.add(lpath);
                    //System.err.println(lpath);
                }
                //Add brackets to attribute comparison
                lFullPathsModified = new HashSet<String>();
                for (String lpath : lFullPaths) {
                    for( Pattern brack_sub : ATTR_SUBST) {
                        Matcher matcher = brack_sub.matcher(lpath);
                        boolean matchFound = matcher.find();

                        if (matchFound) {
                            for (int i=1; i<=matcher.groupCount(); i++) {
                                String groupStr = matcher.group(i);
                                lpath = matcher.replaceAll("["+groupStr+"]");
                            }
                        }
                    }
                    lFullPathsModified.add(lpath);
                }

                allXPaths.addAll(lFullPathsModified);
            }
        }

        for (String xpath : allXPaths) {
            responses.append(xpath).append(NL);
        }
        return responses.toString();
    }


    public static List<String> extractXPaths(String expr) {
          List<String> lFullPaths=null;

                XqueryxStaxParser runparser = new XqueryxStaxParser();
                try {
                String xqueryx_str = runparser.parseXpathToXqueryX(expr);
                List<String> lpathList = runparser.parseXqueryX(xqueryx_str);
                List<Pair<String, Boolean>> combined = runparser.combineOperations(lpathList);
                List<String> boolcomb = runparser.combineBooleans(combined);
                lFullPaths = runparser.getFullPaths(boolcomb);
                } catch (XMLStreamException se) {
                    System.err.println("Could not extract xpaths from xquery " + se);
                }

        return lFullPaths;
    }

  static public String getContents(String filename) {
    StringBuilder contents = new StringBuilder();

    try {
      BufferedReader input =  new BufferedReader(new FileReader(filename));
      try {
        String line;
        while (( line = input.readLine()) != null){
          contents.append(line);
          contents.append(System.getProperty("line.separator"));
        }
      }
      finally {
        input.close();
      }
    }
    catch (IOException ex){
      ex.printStackTrace();
    }

    return contents.toString();
  }
}